package org.zoomdev.zoom.dao.impl;

import org.apache.commons.lang3.StringUtils;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.caster.ValueCaster;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.common.utils.MapUtils;
import org.zoomdev.zoom.dao.Dao;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.Entity;
import org.zoomdev.zoom.dao.EntityField;
import org.zoomdev.zoom.dao.adapters.DataAdapter;
import org.zoomdev.zoom.dao.adapters.StatementAdapter;
import org.zoomdev.zoom.dao.annotations.AutoGenerate;
import org.zoomdev.zoom.dao.annotations.PrimaryKey;
import org.zoomdev.zoom.dao.auto.AutoField;
import org.zoomdev.zoom.dao.auto.DatabaseAutoGenerateKey;
import org.zoomdev.zoom.dao.auto.SequenceAutoGenerateKey;
import org.zoomdev.zoom.dao.meta.ColumnMeta;
import org.zoomdev.zoom.dao.meta.TableMeta;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;

final class BeanEntityFactory extends AbstractEntityFactory {

    private final List<ContextHandler> handlers;


    BeanEntityFactory(Dao dao) {
        super(dao);
        handlers = new ArrayList<>();

        handlers.add(new ValueCasterContextHandler(
                new ValueCasterCreator1(),
                new ValueCasterCreator2(),
                new ValueCasterCreator3(),
                new ValueCasterCreator4()
        ));

        handlers.add(new StatementAdapterContextHandler(
                new StatementAdapterCreator1(),
                new StatementAdapterCreator2()

        ));


        handlers.add(new FillContextHandler());


    }

    static RenameUtils.ColumnRenameConfig search(Map<String, RenameUtils.ColumnRenameConfig> map,
                                                 Field field, String annotationValue) {
        List<RenameUtils.ColumnRenameConfig> set = new ArrayList<RenameUtils.ColumnRenameConfig>();

        for (Map.Entry<String, RenameUtils.ColumnRenameConfig> entry : map.entrySet()) {

            if (annotationValue.equalsIgnoreCase(entry.getKey())) {
                set.add(entry.getValue());
                continue;
            }

            if (entry.getValue().is(annotationValue)) {
                set.add(entry.getValue());
                continue;
            }
        }

        if (set.size() > 1) {
            throw new DaoException("标注的字段" + field + "(" + annotationValue + ")可以找到多个配置" + set);
        } else if (set.isEmpty()) {
            throw new DaoException("标注的字段" + field + "(" + annotationValue + ")找不到对应的配置");
        }

        return set.get(0);
    }

    interface BeanContextHandler extends ContextHandler<CreateContext> {

    }

    class FillContextHandler implements BeanContextHandler {

        @Override
        public void handle(AbstractEntityField entityField, CreateContext context) {
            if (context.config != null) {
                ColumnMeta columnMeta = context.config.columnMeta;
                entityField.setColumnName(context.config.columnName);
                entityField.setSelectColumnName(getSelectColumnName(context.config.selectColumnName));
                entityField.setColumnMeta(columnMeta);
                return;
            }

            if (context.link != null && !StringUtils.isEmpty(context.link.columnName)) {
                entityField.setColumnName(context.link.columnName);
                entityField.setSelectColumnName(getSelectColumnName(context.link.columnName));
                return;
            }

            // 最短距离算法算出哪个字段最接近
            throw new DaoException("绑定实体类" + context.field.getDeclaringClass() + "到表" + context.tableName + "出错，找不到字段的配置" +
                    context.field.getName() + "当前所有可用字段为"
                    + StringUtils.join(context.getAvailableFields(), ","));
        }
    }

    class ValueCasterContextHandler implements BeanContextHandler {

        private final ValueCasterCreator[] creators;

        public ValueCasterContextHandler(ValueCasterCreator... creators) {
            this.creators = creators;
        }

        @Override
        public void handle(AbstractEntityField field, CreateContext context) {
            for (ValueCasterCreator<CreateContext> creator : creators) {
                ValueCaster valueCaster = creator.create(context);
                if (valueCaster != null) {
                    field.setCaster(valueCaster);
                    break;
                }
            }
        }
    }

    class StatementAdapterContextHandler implements BeanContextHandler {

        private final StatementAdapterCreator[] creators;

        public StatementAdapterContextHandler(StatementAdapterCreator... creators) {
            this.creators = creators;
        }

        @Override
        public void handle(AbstractEntityField field, CreateContext context) {
            try {
                for (StatementAdapterCreator<CreateContext> creator : creators) {
                    StatementAdapter statementAdapter = creator.create(context);
                    if (statementAdapter != null) {
                        field.setStatementAdapter(statementAdapter);
                        break;
                    }
                }
            } catch (Throwable e) {
                throw new DaoException("创建StatementAdapter发生异常,field:" +
                        context.field, e);
            }
        }
    }

    class CreateContext {
        final EntityBuilderImpl.LinkPair link;
        final Field field;
        RenameUtils.ColumnRenameConfig config;
        DataAdapter dataAdapter;
        Type[] types;
        TableSource tableName;
        Map<String, RenameUtils.ColumnRenameConfig> map;

        CreateContext(Field field, Map<String, RenameUtils.ColumnRenameConfig> map, EntityBuilderImpl builder) throws Exception {
            this.map = map;
            this.field = field;
            this.link = builder.getLink(field);


            if (this.link != null) {
                if (!StringUtils.isEmpty(link.columnName)) {
                    //映射的字段
                    //看下是否是 aaa_bbbb 或者 table1.aaa_bbb的形式，如果是的话，那么就在map中搜索对应的字段,注意搜索的时候忽略大小写
                    if (EntitySqlUtils.TABLE_AND_COLUMN_PATTERN.matcher(link.columnName).matches()) {
                        //搜索带点好的
                        this.config = search(map, field, link.columnName);
                    }
                } else {
                    this.config = map.get(field.getName());
                }

                this.dataAdapter = this.link.adapter;
                this.types = this.link.types;
            } else {
                this.config = map.get(field.getName());
            }
        }

        public Class<?> getDbType() {
            return config == null ? null : config.columnMeta.getDataType();
        }

        public Set<String> getAvailableFields() {
            return map.keySet();
        }


    }

    class StatementAdapterCreator1 implements StatementAdapterCreator<CreateContext> {

        @Override
        public StatementAdapter create(CreateContext context) {
            if (context.dataAdapter != null) {
                return createWrappedStatementAdapter(context.dataAdapter,
                        dao.getStatementAdapter(Classes.getClass(context.types[1]), context.getDbType()));
            }
            return null;

        }
    }

    class StatementAdapterCreator2 implements StatementAdapterCreator<CreateContext> {

        @Override
        public StatementAdapter create(CreateContext context) {
            if (context.dataAdapter == null) {
                return dao.getStatementAdapter(context.field.getType(), context.getDbType());

            }
            return null;

        }
    }

    class ValueCasterCreator1 implements ValueCasterCreator<CreateContext> {

        @Override
        public ValueCaster create(CreateContext context) {
            if (context.dataAdapter != null && context.config != null) {
                return createWrappedValueCaster(context.dataAdapter,
                        Caster.wrapType(context.config.columnMeta.getDataType(),
                                context.types[1]));
            }
            return null;
        }
    }

    class ValueCasterCreator2 implements ValueCasterCreator<CreateContext> {

        @Override
        public ValueCaster create(CreateContext context) {
            if (context.dataAdapter != null && context.config == null) {
                return createWrappedValueCaster(context.dataAdapter,
                        Caster.wrapFirstVisit(context.types[1]));
            }
            return null;
        }
    }

    class ValueCasterCreator3 implements ValueCasterCreator<CreateContext> {

        @Override
        public ValueCaster create(CreateContext context) {
            if (context.dataAdapter == null && context.config == null) {
                return Caster.wrapFirstVisit(context.field.getGenericType());
            }
            return null;
        }
    }

    class ValueCasterCreator4 implements ValueCasterCreator<CreateContext> {

        @Override
        public ValueCaster create(CreateContext context) {
            if (context.dataAdapter == null && context.config != null) {
                return Caster.wrapType(context.config.columnMeta.getDataType(), context.field.getGenericType());
            }
            return null;
        }
    }

    public Entity createEntity(EntityBuilderImpl tableInfo) {
        Class<?> type = tableInfo.type;
        HashMap<String, RenameUtils.ColumnRenameConfig> map;
        TableSource[] tableNames = tableInfo.getTables();
        TableSource tableName = tableNames[0];
        if (tableInfo.joinMetas != null) {
            map = RenameUtils.rename(dao, tableNames, false);
        } else {
            map = RenameUtils.rename(dao, tableName);
        }

        RenameUtils.ColumnRenameConfig firstTable = map.values().iterator().next();
        TableMeta tableMeta = firstTable.tableMeta;

        Field[] fields = CachedClasses.getFields(type);
        List<AbstractEntityField> entityFields = new ArrayList<>(fields.length);

        for (int index = 0; index < fields.length; ++index) {
            Field field = fields[index];
            // 内部类
            if (field.getName().startsWith("this$")) {
                continue;
            }
            if (tableInfo.isIgnore(field)) {
                continue;
            }
            BeanEntityField entityField = new BeanEntityField(field);
            entityFields.add(entityField);

            try {
                CreateContext context = new CreateContext(field, map, tableInfo);
                context.tableName = tableName;

                for (ContextHandler handler : handlers) {
                    handler.handle(entityField, context);
                }


            } catch (Exception e) {
                if (e instanceof DaoException) {
                    throw (DaoException) e;
                }
                throw new DaoException("绑定Entity失败，发生异常: 实体类:" + type + " 表:" + tableName, e);
            }
        }

        return new BeanEntity(
                tableName,
                entityFields.toArray(new EntityField[0]),
                findPrimaryKeys(entityFields, tableMeta.getColumns(), fields),
                findAutoGenerateFields(entityFields, fields),
                type,
                getNamesMap(map, fields),
                tableInfo.getJoins());
    }

    private EntityField[] findPrimaryKeys(List<AbstractEntityField> entityFields,
                                          ColumnMeta[] columnMetas,
                                          Field[] fields) {
        List<EntityField> primaryKeys = new ArrayList<EntityField>(3);
        for (int i = 0, c = fields.length; i < c; ++i) {
            Field field = fields[i];
            PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
            if (primaryKey != null) {
                primaryKeys.add(entityFields.get(i));
            }
        }
        if (primaryKeys.isEmpty()) {
            for (int i = 0, c = entityFields.size(); i < c; ++i) {

                AbstractEntityField entityField = entityFields.get(i);
                if (entityField.getColumnMeta() != null
                        && entityField.getColumnMeta().isPrimary()) {
                    primaryKeys.add(entityField);
                }
            }
        }

        return primaryKeys.toArray(new EntityField[0]);

    }

    private AutoGenerator findAutoGenerateFields(
            List<AbstractEntityField> entityFields,
            Field[] fields) {
        List<AutoField> autoFields = new ArrayList<>();
        List<EntityField> autoRelatedEntityFields = new ArrayList<EntityField>();
        for (int i = 0, c = entityFields.size(); i < c; ++i) {
            AbstractEntityField entityField = entityFields.get(i);
            AutoField autoField = checkAutoField(fields[i], entityField);
            entityField.setAutoField(autoField);
            if (autoField != null) {
                autoFields.add(autoField);
                autoRelatedEntityFields.add(entityField);
            }
        }


        return createAutoEntity(autoFields, autoRelatedEntityFields);
    }

    /**
     * 在 where/groupGy等语句中的映射关系,除了fields以外的
     *
     * @param map
     * @param fields
     * @return
     */
    private HashMap<String, String> getNamesMap(HashMap<String, RenameUtils.ColumnRenameConfig> map, Field[] fields) {

//        for (Field field : fields) {
//            map.remove(field.getName());
//        }

        return (HashMap<String, String>) MapUtils.convert(map, data -> data.columnName);
    }

    /**
     * 检查一下自增长的key
     *
     * @param field
     * @param entityField
     */
    private AutoField checkAutoField(Field field, AbstractEntityField entityField) {
        AutoGenerate autoGenerate = field.getAnnotation(AutoGenerate.class);
        if (autoGenerate != null) {

            if (!autoGenerate.sequence().isEmpty()) {
                return new SequenceAutoGenerateKey(autoGenerate.sequence());
            }

            return new DatabaseAutoGenerateKey();
        }


        return null;
    }

    private AutoGenerator createAutoEntity(List<AutoField> autoFields, List<EntityField> entityFields) {
        List<String> generatedKeys = new ArrayList<String>();
        List<EntityField> generatedEntityFields = new ArrayList<EntityField>();
        for (int i = 0, c = autoFields.size(); i < c; ++i) {
            AutoField autoField = autoFields.get(i);
            EntityField entityField = entityFields.get(i);
            if (autoField.isDatabaseGeneratedKey()) {
                generatedKeys.add(entityField.getColumnName());
                generatedEntityFields.add(entityField);
            }
        }
        if (!generatedKeys.isEmpty()) {
            return new ZoomAutoGenerator(generatedKeys.toArray(new String[0]),
                    generatedEntityFields.toArray(new EntityField[0]));
        }
        return null;

    }

    private ValueCaster createWrappedValueCaster(
            final DataAdapter dataAdapter, final ValueCaster valueCaster) {

        return src -> {
            Object data = valueCaster.to(src);
            //有可能为null，注意判断
            return dataAdapter.toEntityValue(data);
        };
    }

    private StatementAdapter createWrappedStatementAdapter(final DataAdapter dataAdapter, final StatementAdapter statementAdapter) {
        return (statement, index, value) -> {
            //先转化格式
            value = dataAdapter.toDbValue(value);

            statementAdapter.adapt(statement, index, value);
        };
    }

}
